home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / checks < prev    next >
Encoding:
AWK Script  |  1997-08-26  |  36.0 KB  |  988 lines

  1. #!/usr/local/bin/gawk -f
  2. # @(#) checks.gawk 1.4 96/05/06
  3. # 90/11/10 john h. dubois iii (john@armory.com)
  4. # 91/04/18 Print total at the start of each entry
  5. # 91/09/12 Added ability to read from standard input,
  6. #       and ability to read new checks file format
  7. # 91/12/15 Added check for good transaction value
  8. # 92/09/18 Added help
  9. # 92/09/27 Converted to #!gawk program.
  10. #          Use gawk because it pays attention to changes to ARGC/ARGV.
  11. # 92/11/23 Added -b option
  12. # 93/02/25 Added -r option, made -b the default
  13. # 94/01/01 Made # at start of check-num be acted on in all cases
  14. # 94/04/23 Use .checks file.
  15. # 95/04/01 More strict checking of amount field.  Allow either tabs or spaces
  16. #          to separate fields.  Format line when printing running balance.
  17. # 95/04/28 Added t option.
  18. # 95/06/18 Added Mn options.
  19. # 96/05/06 Search only for CHECKS in the environment.
  20.  
  21. BEGIN {
  22.     Name = "checks"
  23.     rcFile = ".checks"
  24.     Usage = "Usage: " Name " [-rtdhnM] [checks-file ...]"
  25.     ARGC = Opts(Name,Usage,"f:rtdhnMx",0,"~/" rcFile,"CHECKS,RUNNING,TOP",1)
  26.     if ("h" in Options) {
  27.     printf \
  28. Name ": process checking account log files.\n"\
  29. Usage "\n"\
  30. "If no files are given, the file given in the environment variable CHECKS\n"\
  31. "is used.  CHECKS can also be set in the file %s in the invoking user's\n"\
  32. "home directory in this form:\n"\
  33. "CHECKS=filename\n"\
  34. "If no filenames are given and CHECKS is not set, the standard input is\n"\
  35. "used.  If a file name of \"-\" is given, the standard input is read even\n"\
  36. "if CHECKS is set.\n" \
  37. "Options:\n"\
  38. "-r: Print entries with a running balance as they are processed.\n"\
  39. "    Set RUNNING in %s.\n"\
  40. "-n: Print running balance lines only when the balance is negative.\n"\
  41. "-t: Like -r, but prints only as many lines as will fit on the screen\n"\
  42. "    (the last lines processed), and truncates them to the screen width.\n"\
  43. "    Set TOP in %s.\n"\
  44. "-M: Print the minimum and maximum balances found.\n"\
  45. "-h: Print this help.\n" \
  46. "-d: Print a description of the format of a checks file.\n",
  47.      rcFile,rcFile,rcFile
  48.     Err = "0"
  49.     exit 0
  50.     }
  51.  
  52.     if ("d" in Options) {
  53.     print \
  54. "Format of a checks file:\n"\
  55. "A checks (checking account transactions) file consists of records formed\n"\
  56. "from lines which each contain up to 6 fields separated by single tabs or\n"\
  57. "spaces, as follows:\n"\
  58. "\n"\
  59. "StatementDate<fs>CheckDate<fs>CheckNum<fs>Amount<fs>To/From<fs>WhatFor\n"\
  60. "\n"\
  61. "The statement date is the date that the transaction appears on a checking\n"\
  62. "account statement.  It is not used by the checks program (except that if\n"\
  63. "it begins with a '#', then the line is ignored), but can be used to sort\n"\
  64. "the checks file so that the correct account balance appears.  It should\n"\
  65. "initially be set to 100 and changed to a date when the transaction appears\n"\
  66. "on a checking account statement.  This will ensure that transactions which\n"\
  67. "have not yet appeared on a statement will sort to the bottom, so that the\n"\
  68. "correct balance for the last statement that was received will appear\n"\
  69. "immediately after the transactions recorded on that statement for\n"\
  70. "comparision to the balance on the statement.\n"\
  71. "CheckDate is the date recorded on the check or other transaction.\n"\
  72. "CheckNum is the number of the check.  They are not used by the checks\n"\
  73. "program, except that if either begins with a '#' the line is ignored, and\n"\
  74. "if they are both empty the line is considered a continuation of the\n"\
  75. "transaction record that the previous line is part of.  All such\n"\
  76. "continuation lines are read before the transaction is printed, and a\n"\
  77. "single new balance is printed for the transaction.\n"\
  78. "\n"\
  79. "amount is the amount of the transaction.  It should be negative for\n"\
  80. "withdrawals and positive for deposits.\n"\
  81. "\n"\
  82. "To/From is used to record who the transaction was with.  whatfor is used\n"\
  83. "to describe what the transaction was for.  Neither is used by the checks\n"\
  84. "program.  Other utilities that process checks files may require that the\n"\
  85. "To/From and WhatFor fields be separated from each other by a tab, not a\n"\
  86. "space, if they use either of the fields, since these fields may themselves\n"\
  87. "contain spaces."
  88.     Err = "0"
  89.     exit(0)
  90.     }
  91.  
  92.     Debug = "x" in Options
  93.     Negative = "n" in Options
  94.     MinMax = "M" in Options
  95.     BalanceOnly = !("r" in Options || "t" in Options || Negative)
  96.     if (ARGC < 2) {
  97.     if ("f" in Options) {
  98.         ARGV[1] = Options["f"]
  99.         ARGC = 2
  100.     }
  101.     else {
  102.         print "No checks file.  Use -h for help." > "/dev/stderr"
  103.         Err = 1
  104.         exit
  105.     }
  106.     }
  107.  
  108.     FS = "[\t ]"
  109.     # Generate pattern to match 1st 4 fields
  110.     Field = "[^ \t]*[ \t]"
  111.     SubPat = "^" Field Field Field Field
  112.     Entry = ""
  113.     Sum = PrevSum = 0
  114.     if ("t" in Options)
  115.     HeadTailInit()
  116. }
  117.  
  118. Debug {
  119.     print("Input line: " $0)
  120. }
  121.  
  122. function PrintLine(Sum,Line) {
  123.     if (!Negative || Sum < 0)
  124.     TailPrint(sprintf("%9.2f %s",Sum,Line))
  125. }
  126.  
  127. # Munges $0!!!
  128. function FormatLine(  Line) {
  129.     Line = sprintf("%5s %8s %5s %8s ",$1,$2,$3,$4)
  130.     sub(SubPat,"",$0)
  131.     return Line $0
  132. }
  133.  
  134. # Balance processing occurs only here.
  135. {
  136.     # A # at the start of field 1, 2, or 3 indicates a comment.
  137.     if (!(($1 ~ "^#") || ($2 ~ "^#") || ($3 ~ "^#")))
  138.     if ($4 !~ /^-?(\.?[0-9]+|[0-9]+\.[0-9]+)$/)
  139.         printf "**** Error on line %d: bad transaction value \"%s\".\n",
  140.         NR,$4 | "cat 1>&2"
  141.     else {
  142.         Sum += $4
  143.         if (MinMax) {
  144.         if (Sum < MinBal) {
  145.             MinBal = Sum
  146.             MinDate = $2
  147.         }
  148.         if (Sum > MaxBal) {
  149.             MaxBal = Sum
  150.             MaxDate = $2
  151.         }
  152.         }
  153.     }
  154. }
  155.  
  156. # Accumulate an entry.  This is neccessary because the running balance is only
  157. # printed once, at the start of the entry, but the balance isn't known until
  158. # the entire entry has been processed.
  159. # This block is used only as part of printing a running balance.
  160. !BalanceOnly {
  161.     # A non-null 2nd or 3rd field indicates that a new entry has started.
  162.     # Print out accumulated entry & clear entry accumulator.
  163.     # Also start a new entry if a comment is found.
  164.     # Use the sum saved from before the current line was added to it.
  165.     # Print only if Entry is non-null so that printing will not occur when
  166.     # the first line is read.
  167.     if (Entry != "" && (($1 ~ "^#") || ($2 != "") || ($3 != ""))) {
  168.     PrintLine(PrevSum,Entry)
  169.     Entry = FormatLine()
  170.     }
  171.     else if (Entry != "")
  172.     Entry = Entry "\n" sprintf("%9s %s","",FormatLine())
  173.     else
  174.     Entry = sprintf("%9s %s","",FormatLine())
  175.     PrevSum = Sum
  176. }
  177.  
  178. END {
  179.     if (Err != "")
  180.     exit(Err)
  181.     if (!BalanceOnly)
  182.     PrintLine(Sum,Entry)
  183.     # Don't print . after balance, so that it's easier to select in xterm
  184.     TailPrint(sprintf("Balance: %.2f",Sum))
  185.     if (MinMax) {
  186.     TailPrint(sprintf("Minimum balance: %s %.2f",MinDate,MinBal))
  187.     TailPrint(sprintf("Maximum balance: %s %.2f",MaxDate,MaxBal))
  188.     }
  189.     TailFlush()
  190.  
  191. # Start of library routines
  192.  
  193. # @(#) headtail.awk 95/04/18
  194. # 95/04/28 Added tail routines.
  195.  
  196. # Turn on screen-bounded printing.
  197. # Sets global vars LINES and COLUMNS.
  198. # Set either of them to 0 after calling this function if you do not want
  199. # limiting of lines or line length respectively.
  200. function HeadTailInit() {
  201.     # tput will use values in environment, but we want to avoid running
  202.     # it if possible.
  203.     if ("COLUMNS" in ENVIRON)
  204.     COLUMNS = ENVIRON["COLUMNS"]
  205.     else {
  206.     Cmd = "tput cols"
  207.     Cmd | getline COLUMNS
  208.     close(Cmd)
  209.     if (COLUMNS == "")
  210.         COLUMNS = 80
  211.     }
  212.     if ("LINES" in ENVIRON)
  213.     LINES = ENVIRON["LINES"]
  214.     else {
  215.     Cmd = "tput lINES"
  216.     Cmd | getline LINES
  217.     close(Cmd)
  218.     if (LINES == "")
  219.         LINES = 24
  220.     }
  221. }
  222.  
  223. # Do screen-bound printing.  
  224. # If LINES  is >0, the last LINES-1 lines are kept in a circular buffer.  
  225. # When TailFlush() is called, they are printed.
  226. # If LINES = 0, all lines are printed immediately.
  227. # If COLUMNS is >0, truncates Line to COLUMNS-1 characters before printing it.
  228. # Global vars: uses LINES & COLUMNS; sets/uses TailPtr;
  229. # saves lines in TailLines[] from 1..LINES-1
  230. # Embedded newlines split the line into multiple lines; trailing newlines are
  231. # stripped.  Tabs are expanded to spaces.
  232. function TailPrint(Line) {
  233.     if (!LINES)
  234.     print Line
  235.     else {
  236.     if (++TailPtr > (LINES-1))
  237.         TailPtr = 1
  238.     TailLines[TailPtr] = Line
  239.     }
  240. }
  241.  
  242. function TailFlush(  NumPrinted,Lines,Line,i,Buffer,PrintLines) {
  243.     if (!LINES)
  244.     return
  245.     NumPrinted = 0
  246.     PrintLines = LINES-1
  247.     # Since lines may contain multiple lines, we must create a buffer to be
  248.     # printed by reading line buffer backwards.
  249.     # Stop when we've copied enough lines, or if we wrap around to the end and
  250.     # find that the entire line buffer wasn't used.
  251.     while (NumPrinted < PrintLines && TailPtr in TailLines) {
  252.     # Split line into individual lines, then process them last to first
  253.     Num = split(TailLines[TailPtr],Lines,"\n")
  254.     for (i = Num; i >= 1; i--) {
  255.         Line = Lines[i]
  256.         if (i == Num && Line == "")    # discard trailing newline
  257.         continue
  258.         # Put this line at the front of the print buffer
  259.         if (COLUMNS)
  260.         Buffer = substr(TabEx(Line),1,COLUMNS - 1) "\n" Buffer
  261.         else
  262.         Buffer = Line "\n" Buffer
  263.         if (++NumPrinted == PrintLines)
  264.         break
  265.     }
  266.     if (!--TailPtr)    # Wrap pointer if neccessary
  267.         TailPtr = PrintLines
  268.     }
  269.     printf "%s",Buffer
  270. }
  271.  
  272. # Do screen-bound printing.  
  273. # If LINES >0, returns 0 when LINES-1 lines have been printed by HeadPrint().
  274. # Otherwise returns 1.
  275. # If COLUMNS is >0, truncates Line to COLUMNS-1 characters before printing it.
  276. # Global vars: uses LINES & COLUMNS; sets/uses LinesPrinted.
  277. # Line should not include newlines.
  278. function HeadPrint(Line) {
  279.     # Check first, in case some calls of this function to not check return
  280.     # value, and in case LINES is 1.
  281.     if (LINES && LinesPrinted >= (LINES-1))
  282.     return 0
  283.     if (COLUMNS)
  284.     print substr(Line,1,COLUMNS - 1)
  285.     else
  286.     print Line
  287.     if (LINES && ++LinesPrinted >= (LINES-1))
  288.     return 0
  289.     return 1
  290. }
  291.  
  292. # Expand tabs in Line
  293. function TabEx(Line,  Segs,i,Num,S) {
  294.     Num = split(Line,Segs,"\t")
  295.     Line = ""
  296.     for (i = 1; i < Num; i++) {
  297.     S = Segs[i]
  298.     Line = Line S substr("        ",length(S) % 8 + 1)
  299.     }
  300.     return Line Segs[Num]
  301. }
  302.  
  303. ### Start of ProcArgs library
  304. # @(#) ProcArgs 1.11 96/12/08
  305. # 92/02/29 john h. dubois iii (john@armory.com)
  306. # 93/07/18 Added "#" arg type
  307. # 93/09/26 Do not count -h against MinArgs
  308. # 94/01/01 Stop scanning at first non-option arg.  Added ">" option type.
  309. #          Removed meaning of "+" or "-" by itself.
  310. # 94/03/08 Added & option and *()< option types.
  311. # 94/04/02 Added NoRCopt to Opts()
  312. # 94/06/11 Mark numeric variables as such.
  313. # 94/07/08 Opts(): Do not require any args if h option is given.
  314. # 95/01/22 Record options given more than once.  Record option num in argv.
  315. # 95/06/08 Added ExclusiveOptions().
  316. # 96/01/20 Let rcfiles be a colon-separated list of filenames.
  317. #          Expand $VARNAME at the start of its filenames.
  318. #          Let varname=0 and -option- turn off an option.
  319. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
  320. #          of the vars should be searched for in the environment.
  321. #          Check for duplicate rcfiles.
  322. # 96/05/13 Return more specific error values.  Note: ProcArgs() and InitOpts()
  323. #          now return various negatives values on error, not just -1, and
  324. #          Opts() may set Err to various positive values, not just 1.
  325. #          Added AllowUnrecOpt.
  326. # 96/05/23 Check type given for & option
  327. # 96/06/15 Re-port to awk
  328. # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
  329. #          used by other functions.
  330. # 96/10/15 Added OptChars
  331. # 96/11/01 Added exOpts arg to Opts()
  332. # 96/11/16 Added ; type
  333. # 96/12/08 Added Opt2Set() & Opt2Sets()
  334. # 96/12/27 Added CmdLineOpt()
  335.  
  336. # optlist is a string which contains all of the possible command line options.
  337. # A character followed by certain characters indicates that the option takes
  338. # an argument, with type as follows:
  339. # :    String argument
  340. # ;    Non-empty string argument
  341. # *    Floating point argument
  342. # (    Non-negative floating point argument
  343. # )    Positive floating point argument
  344. # #    Integer argument
  345. # <    Non-negative integer argument
  346. # >    Positive integer argument
  347. # The only difference the type of argument makes is in the runtime argument
  348. # error checking that is done.
  349.  
  350. # The & option is a special case used to get numeric options without the
  351. # user having to give an option character.  It is shorthand for [-+.0-9].
  352. # If & is included in optlist and an option string that begins with one of
  353. # these characters is seen, the value given to "&" will include the first
  354. # char of the option.  & must be followed by a type character other than ":"
  355. # or ";".
  356. # Note that if e.g. &> is given, an option of -.5 will produce an error.
  357.  
  358. # Strings in argv[] which begin with "-" or "+" are taken to be
  359. # strings of options, except that a string which consists solely of "-"
  360. # or "+" is taken to be a non-option string; like other non-option strings,
  361. # it stops the scanning of argv and is left in argv[].
  362. # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
  363. # If an option takes an argument, the argument may either immediately
  364. # follow it or be given separately.
  365. # "-" and "+" options are treated the same.  "+" is allowed because most awks
  366. # take any -options to be arguments to themselves.  gawk 2.15 was enhanced to
  367. # stop scanning when it encounters an unrecognized option, though until 2.15.5
  368. # this feature had a flaw that caused problems in some cases.  See the OptChars
  369. # parameter to explicitly set the option-specifier characters.
  370.  
  371. # If an option that does not take an argument is given,
  372. # an index with its name is created in Options and its value is set to the
  373. # number of times it occurs in argv[].
  374.  
  375. # If an option that does take an argument is given, an index with its name is
  376. # created in Options and its value is set to the value of the argument given
  377. # for it, and Options[option-name,"count"] is (initially) set to the 1.
  378. # If an option that takes an argument is given more than once,
  379. # Options[option-name,"count"] is incremented, and the value is assigned to
  380. # the index (option-name,instance) where instance is 2 for the second occurance
  381. # of the option, etc.
  382. # In other words, the first time an option with a value is encountered, the
  383. # value is assigned to an index consisting only of its name; for any further
  384. # occurances of the option, the value index has an extra (count) dimension.
  385.  
  386. # The sequence number for each option found in argv[] is stored in
  387. # Options[option-name,"num",instance], where instance is 1 for the first
  388. # occurance of the option, etc.  The sequence number starts at 1 and is
  389. # incremented for each option, both those that have a value and those that
  390. # do not.  Options set from a config file have a value of 0 assigned to this.
  391.  
  392. # Options and their arguments are deleted from argv.
  393. # Note that this means that there may be gaps left in the indices of argv[].
  394. # If compress is nonzero, argv[] is packed by moving its elements so that
  395. # they have contiguous integer indices starting with 0.
  396. # Option processing will stop with the first unrecognized option, just as
  397. # though -- was given except that unlike -- the unrecognized option will not be
  398. # removed from ARGV[].  Normally, an error value is returned in this case.
  399. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
  400. # be found, so the number of remaining arguments is returned instead.
  401. # If OptChars is not a null string, it is the set of characters that indicate
  402. # that an argument is an option string if the string begins with one of the
  403. # characters.  A string consisting solely of two of the same option-indicator
  404. # characters stops the scanning of argv[].  The default is "-+".
  405. # argv[0] is not examined.
  406. # The number of arguments left in argc is returned.
  407. # If an error occurs, the global string OptErr is set to an error message
  408. # and a negative value is returned.
  409. # Current error values:
  410. # -1: option that required an argument did not get it.
  411. # -2: argument of incorrect type supplied for an option.
  412. # -3: unrecognized (invalid) option.
  413. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
  414. ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
  415. NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
  416. {
  417. # ArgNum is the index of the argument being processed.
  418. # ArgsLeft is the number of arguments left in argv.
  419. # Arg is the argument being processed.
  420. # ArgLen is the length of the argument being processed.
  421. # ArgInd is the position of the character in Arg being processed.
  422. # Option is the character in Arg being processed.
  423. # Pos is the position in OptList of the option being processed.
  424. # NumOpt is true if a numeric option may be given.
  425.     ArgsLeft = argc
  426.     NumOpt = index(OptList,"&")
  427.     OptionNum = 0
  428.     if (OptChars == "")
  429.     OptChars = "-+"
  430.     while (OptChars != "") {
  431.     c = substr(OptChars,1,1)
  432.     OptChars = substr(OptChars,2)
  433.     OptCharSet[c]
  434.     OptTerm[c c]
  435.     }
  436.     for (ArgNum = 1; ArgNum < argc; ArgNum++) {
  437.     Arg = argv[ArgNum]
  438.     if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
  439.         break    # Not an option; quit
  440.     if (Arg in OptTerm) {
  441.         delete argv[ArgNum]
  442.         ArgsLeft--
  443.         break
  444.     }
  445.     ArgLen = length(Arg)
  446.     for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
  447.         Option = substr(Arg,ArgInd,1)
  448.         if (NumOpt && Option ~ /[-+.0-9]/) {
  449.         # If this option is a numeric option, make its flag be & and
  450.         # its option string flag position be the position of & in
  451.         # the option string.
  452.         Option = "&"
  453.         Pos = NumOpt
  454.         # Prefix Arg with a char so that ArgInd will point to the
  455.         # first char of the numeric option.
  456.         Arg = "&" Arg
  457.         ArgLen++
  458.         }
  459.         # Find position of flag in option string, to get its type (if any).
  460.         # Disallow & as literal flag.
  461.         else if (!(Pos = index(OptList,Option)) || Option == "&") {
  462.         if (AllowUnrecOpt) {
  463.             Escape = 1
  464.             break
  465.         }
  466.         else {
  467.             OptErr = "Invalid option: " specGiven Option
  468.             return -3
  469.         }
  470.         }
  471.  
  472.         # Find what the value of the option will be if it takes one.
  473.         # NeedNextOpt is true if the option specifier is the last char of
  474.         # this arg, which means that if the option requires a value it is
  475.         # the next arg.
  476.         if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
  477.         if (GotValue = ArgNum + 1 < argc)
  478.             Value = argv[ArgNum+1]
  479.         }
  480.         else {    # Value is included with option
  481.         Value = substr(Arg,ArgInd + 1)
  482.         GotValue = 1
  483.         }
  484.  
  485.         if (HadValue = AssignVal(Option,Value,Options,
  486.         substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
  487.         specGiven)) {
  488.         if (HadValue < 0)    # error occured
  489.             return HadValue
  490.         if (HadValue == 2)
  491.             ArgInd++    # Account for the single-char value we used.
  492.         else {
  493.             if (NeedNextOpt) {    # option took next arg as value
  494.             delete argv[++ArgNum]
  495.             ArgsLeft--
  496.             }
  497.             break    # This option has been used up
  498.         }
  499.         }
  500.     }
  501.     if (Escape)
  502.         break
  503.     # Do not delete arg until after processing of it, so that if it is not
  504.     # recognized it can be left in ARGV[].
  505.     delete argv[ArgNum]
  506.     ArgsLeft--
  507.     }
  508.     if (compress != 0) {
  509.     dest = 1
  510.     src = argc - ArgsLeft + 1
  511.     for (count = ArgsLeft - 1; count; count--) {
  512.         ARGV[dest] = ARGV[src]
  513.         dest++
  514.         src++
  515.     }
  516.     }
  517.     return ArgsLeft
  518. }
  519.  
  520. # Assignment to values in Options[] occurs only in this function.
  521. # Option: Option specifier character.
  522. # Value: Value to be assigned to option, if it takes a value.
  523. # Options[]: Options array to return values in.
  524. # ArgType: Argument type specifier character.
  525. # GotValue: Whether any value is available to be assigned to this option.
  526. # Name: Name of option being processed.
  527. # OptionNum: Number of this option (starting with 1) if set in argv[],
  528. #     or 0 if it was given in a config file or in the environment.
  529. # SingleOpt: true if the value (if any) that is available for this option was
  530. #     given as part of the same command line arg as the option.  Used only for
  531. #     options from the command line.
  532. # specGiven is the option specifier character use, if any (e.g. - or +),
  533. # for use in error messages.
  534. # Global variables: OptErr
  535. # Return value: negative value on error, 0 if option did not require an
  536. # argument, 1 if it did & used the whole arg, 2 if it required just one char of
  537. # the arg.
  538. # Current error values:
  539. # -1: Option that required an argument did not get it.
  540. # -2: Value of incorrect type supplied for option.
  541. # -3: Bad type given for option &
  542. function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
  543. SingleOpt,specGiven,  UsedValue,Err,NumTypes) {
  544.     # If option takes a value...    [
  545.     NumTypes = "*()#<>]"
  546.     if (Option == "&" && ArgType !~ "[" NumTypes) {    # ]
  547.     OptErr = "Bad type given for & option"
  548.     return -3
  549.     }
  550.  
  551.     if (UsedValue = (ArgType ~ "[:;" NumTypes)) {    # ]
  552.     if (!GotValue) {
  553.         if (Name != "")
  554.         OptErr = "Variable requires a value -- " Name
  555.         else
  556.         OptErr = "option requires an argument -- " Option
  557.         return -1
  558.     }
  559.     if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
  560.         OptErr = Err
  561.         return -2
  562.     }
  563.     # Mark this as a numeric variable; will be propogated to Options[] val.
  564.     if (ArgType != ":" && ArgType != ";")
  565.         Value += 0
  566.     if ((Instance = ++Options[Option,"count"]) > 1)
  567.         Options[Option,Instance] = Value
  568.     else
  569.         Options[Option] = Value
  570.     }
  571.     # If this is an environ or rcfile assignment & it was given a value...
  572.     else if (!OptionNum && Value != "") {
  573.     UsedValue = 1
  574.     # If the value is "0" or "-" and this is the first instance of it,
  575.     # do not set Options[Option]; this allows an assignment in an rcfile to
  576.     # turn off an option (for the simple "Option in Options" test) in such
  577.     # a way that it cannot be turned on in a later file.
  578.     if (!(Option in Options) && (Value == "0" || Value == "-"))
  579.         Instance = 1
  580.     else
  581.         Instance = ++Options[Option]
  582.     # Save the value even though this is a flag
  583.     Options[Option,Instance] = Value
  584.     }
  585.     # If this is a command line flag and has a - following it in the same arg,
  586.     # it is being turned off.
  587.     else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
  588.     UsedValue = 2
  589.     if (Option in Options)
  590.         Instance = ++Options[Option]
  591.     else
  592.         Instance = 1
  593.     Options[Option,Instance]
  594.     }
  595.     # If this is a flag assignment without a value, increment the count for the
  596.     # flag unless it was turned off.  The indicator for a flag being turned off
  597.     # is that the flag index has not been set in Options[] but it has an
  598.     # instance count.
  599.     else if (Option in Options || !((Option,1) in Options))
  600.     # Increment number of times this flag seen; will inc null value to 1
  601.     Instance = ++Options[Option]
  602.     Options[Option,"num",Instance] = OptionNum
  603.     return UsedValue
  604. }
  605.  
  606. # Option is the option letter
  607. # Value is the value being assigned
  608. # Name is the var name of the option, if any
  609. # ArgType is one of:
  610. # :    String argument
  611. # ;    Non-null string argument
  612. # *    Floating point argument
  613. # (    Non-negative floating point argument
  614. # )    Positive floating point argument
  615. # #    Integer argument
  616. # <    Non-negative integer argument
  617. # >    Positive integer argument
  618. # specGiven is the option specifier character use, if any (e.g. - or +),
  619. # for use in error messages.
  620. # Returns null on success, err string on error
  621. function CheckType(ArgType,Value,Option,Name,specGiven,  Err,ErrStr) {
  622.     if (ArgType == ":")
  623.     return ""
  624.     if (ArgType == ";") {
  625.     if (Value == "")
  626.         Err = "must be a non-empty string"
  627.     }
  628.     # A number begins with optional + or -, and is followed by a string of
  629.     # digits or a decimal with digits before it, after it, or both
  630.     else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
  631.     Err = "must be a number"
  632.     else if (ArgType ~ "[#<>]" && Value ~ /\./)
  633.     Err = "may not include a fraction"
  634.     else if (ArgType ~ "[()<>]" && Value < 0)
  635.     Err = "may not be negative"
  636.     # (
  637.     else if (ArgType ~ "[)>]" && Value == 0)
  638.     Err = "must be a positive number"
  639.     if (Err != "") {
  640.     ErrStr = "Bad value \"" Value "\".  Value assigned to "
  641.     if (Name != "")
  642.         return ErrStr "variable " substr(Name,1,1) " " Err
  643.     else {
  644.         if (Option == "&")
  645.         Option = Value
  646.         return ErrStr "option " specGiven substr(Option,1,1) " " Err
  647.     }
  648.     }
  649.     else
  650.     return ""
  651. }
  652.  
  653. # Note: only the above functions are needed by ProcArgs.
  654. # The rest of these functions call ProcArgs() and also do other
  655. # option-processing stuff.
  656.  
  657. # Opts: Process command line arguments.
  658. # Opts processes command line arguments using ProcArgs()
  659. # and checks for errors.  If an error occurs, a message is printed
  660. # and the program is exited.
  661. #
  662. # Input variables:
  663. # Name is the name of the program, for error messages.
  664. # Usage is a usage message, for error messages.
  665. # OptList the option description string, as used by ProcArgs().
  666. # MinArgs is the minimum number of non-option arguments that this
  667. # program should have, non including ARGV[0] and +h.
  668. # If the program does not require any non-option arguments,
  669. # MinArgs should be omitted or given as 0.
  670. # rcFiles, if given, is a colon-seprated list of filenames to read for
  671. # variable initialization.  If a filename begins with ~/, the ~ is replaced
  672. # by the value of the environment variable HOME.  If a filename begins with
  673. # $, the part from the character after the $ up until (but not including)
  674. # the first character not in [a-zA-Z0-9_] will be searched for in the
  675. # environment; if found its value will be substituted, if not the filename will
  676. # be discarded.
  677. # rcfiles are read in the order given.
  678. # Values given in them will not override values given on the command line,
  679. # and values given in later files will not override those set in earlier
  680. # files, because AssignVal() will store each with a different instance index.
  681. # The first instance of each variable, either on the command line or in an
  682. # rcfile, will be stored with no instance index, and this is the value
  683. # normally used by programs that call this function.
  684. # VarNames is a comma-separated list of variable names to map to options,
  685. # in the same order as the options are given in OptList.
  686. # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
  687. # searched for in the environment.  If set to -1, all values will be searched
  688. # for in the environment.  Values given in the environment will override
  689. # those given in the rcfiles but not those given on the command line.
  690. # NoRCopt, if given, is an additional letter option that if given on the
  691. # command line prevents the rcfiles from being read.
  692. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
  693. # ExclusiveOptions() for a description of exOpts.
  694. # Special options:
  695. # If x is made an option and is given, some debugging info is output.
  696. # h is assumed to be the help option.
  697.  
  698. # Global variables:
  699. # The command line arguments are taken from ARGV[].
  700. # The arguments that are option specifiers and values are removed from
  701. # ARGV[], leaving only ARGV[0] and the non-option arguments.
  702. # The number of elements in ARGV[] should be in ARGC.
  703. # After processing, ARGC is set to the number of elements left in ARGV[].
  704. # The option values are put in Options[].
  705. # On error, Err is set to a positive integer value so it can be checked for in
  706. # an END block.
  707. # Return value: The number of elements left in ARGV is returned.
  708. # Must keep OptErr global since it may be set by InitOpts().
  709. function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
  710. AllowUnrecOpt,optChars,exOpts,  ArgsLeft,e) {
  711.     if (MinArgs == "")
  712.     MinArgs = 0
  713.     ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
  714.     optChars)
  715.     if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
  716.     if (ArgsLeft >= 0) {
  717.         OptErr = "Not enough arguments"
  718.         Err = 4
  719.     }
  720.     else
  721.         Err = -ArgsLeft
  722.     printf "%s: %s.\nUse -h for help.\n%s\n",
  723.     Name,OptErr,Usage > "/dev/stderr"
  724.     exit 1
  725.     }
  726.     if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
  727.     (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
  728.     {
  729.     print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
  730.     Err = -e
  731.     exit 1
  732.     }
  733.     if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
  734.     {
  735.     printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
  736.     Err = 1
  737.     exit 1
  738.     }
  739.     return ArgsLeft
  740. }
  741.  
  742. # ReadConfFile(): Read a file containing var/value assignments, in the form
  743. # <variable-name><assignment-char><value>.
  744. # Whitespace (spaces and tabs) around a variable (leading whitespace on the
  745. # line and whitespace between the variable name and the assignment character) 
  746. # is stripped.  Lines that do not contain an assignment operator or which
  747. # contain a null variable name are ignored, other than possibly being noted in
  748. # the return value.  If more than one assignment is made to a variable, the
  749. # first assignment is used.
  750. # Input variables:
  751. # File is the file to read.
  752. # Comment is the line-comment character.  If it is found as the first non-
  753. #     whitespace character on a line, the line is ignored.
  754. # Assign is the assignment string.  The first instance of Assign on a line
  755. #     separates the variable name from its value.
  756. # If StripWhite is true, whitespace around the value (whitespace between the
  757. #     assignment char and trailing whitespace on the line) is stripped.
  758. # VarPat is a pattern that variable names must match.  
  759. #     Example: "^[a-zA-Z][a-zA-Z0-9]+$"
  760. # If FlagsOK is true, variables are allowed to be "set" by being put alone on
  761. #     a line; no assignment operator is needed.  These variables are set in
  762. #     the output array with a null value.  Lines containing nothing but
  763. #     whitespace are still ignored.
  764. # Output variables:
  765. # Values[] contains the assignments, with the indexes being the variable names
  766. #     and the values being the assigned values.
  767. # Lines[] contains the line number that each variable occured on.  A flag set
  768. #     is record by giving it an index in Lines[] but not in Values[].
  769. # Return value:
  770. # If any errors occur, a string consisting of descriptions of the errors
  771. # separated by newlines is returned.  In no case will the string start with a
  772. # numeric value.  If no errors occur,  the number of lines read is returned.
  773. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
  774. FlagsOK,
  775. Line,Status,Errs,AssignLen,LineNum,Var,Val) {
  776.     if (Comment != "")
  777.     Comment = "^" Comment
  778.     AssignLen = length(Assign)
  779.     if (VarPat == "")
  780.     VarPat = "."    # null varname not allowed
  781.     while ((Status = (getline Line < File)) == 1) {
  782.     LineNum++
  783.     sub("^[ \t]+","",Line)
  784.     if (Line == "")        # blank line
  785.         continue
  786.     if (Comment != "" && Line ~ Comment)
  787.         continue
  788.     if (Pos = index(Line,Assign)) {
  789.         Var = substr(Line,1,Pos-1)
  790.         Val = substr(Line,Pos+AssignLen)
  791.         if (StripWhite) {
  792.         sub("^[ \t]+","",Val)
  793.         sub("[ \t]+$","",Val)
  794.         }
  795.     }
  796.     else {
  797.         Var = Line    # If no value, var is entire line
  798.         Val = ""
  799.     }
  800.     if (!FlagsOK && Val == "") {
  801.         Errs = Errs \
  802.         sprintf("\nBad assignment on line %d of file %s: %s",
  803.         LineNum,File,Line)
  804.         continue
  805.     }
  806.     sub("[ \t]+$","",Var)
  807.     if (Var !~ VarPat) {
  808.         Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
  809.         LineNum,File,Var)
  810.         continue
  811.     }
  812.     if (!(Var in Lines)) {
  813.         Lines[Var] = LineNum
  814.         if (Pos)
  815.         Values[Var] = Val
  816.     }
  817.     }
  818.     if (Status)
  819.     Errs = Errs "\nCould not read file " File
  820.     close(File)
  821.     return Errs == "" ? LineNum : substr(Errs,2)    # Skip first newline
  822. }
  823.  
  824. # Variables:
  825. # Data is stored in Options[].
  826. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
  827. # Global vars:
  828. # Sets OptErr.  Uses ENVIRON[].
  829. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
  830. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
  831. Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
  832. fNames,numrcFiles,filesRead,Err,Values,retStr) {
  833.     split("",filesRead,"")    # make awk know this is an array
  834.     NumVars = split(VarNames,Vars,",")
  835.     TypesInd = Ret = 0
  836.     if (EnvSearch == -1)
  837.     EnvSearch = NumVars
  838.     for (i = 1; i <= NumVars; i++) {
  839.     Var = Vars[i]
  840.     CharOpt = substr(OptList,++TypesInd,1)
  841.     if (CharOpt ~ "^[:;*()#<>&]$")
  842.         CharOpt = substr(OptList,++TypesInd,1)
  843.     Map[Var] = CharOpt
  844.     Types[Var] = Type = substr(OptList,TypesInd+1,1)
  845.     # Do not overwrite entries from environment
  846.     if (i <= EnvSearch && Var in ENVIRON &&
  847.     (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0)
  848.         return Err
  849.     }
  850.  
  851.     numrcFiles = split(rcFiles,fNames,":")
  852.     for (i = 1; i <= numrcFiles; i++) {
  853.     rcFile = fNames[i]
  854.     if (rcFile ~ "^~/")
  855.         rcFile = ENVIRON["HOME"] substr(rcFile,2)
  856.     else if (rcFile ~ /^\$/) {
  857.         rcFile = substr(rcFile,2)
  858.         match(rcFile,"^[a-zA-Z0-9_]*")
  859.         envvar = substr(rcFile,1,RLENGTH)
  860.         if (envvar in ENVIRON)
  861.         rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
  862.         else
  863.         continue
  864.     }
  865.     if (rcFile in filesRead)
  866.         continue
  867.     # rcfiles are liable to be given more than once, e.g. UHOME and HOME
  868.     # may be the same
  869.     filesRead[rcFile]
  870.     if ("x" in Options)
  871.         printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
  872.     retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
  873.     if (retStr > 0)
  874.         READ_RCFILE = 1
  875.     else if (ret != "") {
  876.         OptErr = retStr
  877.         Ret = -1
  878.     }
  879.     for (Var in Lines)
  880.         if (Var in Map) {
  881.         if ((Err = AssignVal(Map[Var],
  882.         Var in Values ? Values[Var] : "",Options,Types[Var],
  883.         Var in Values,Var,0)) < 0)
  884.             return Err
  885.         }
  886.         else {
  887.         OptErr = sprintf(\
  888.         "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
  889.         Lines[Var],rcFile)
  890.         Ret = -1
  891.         }
  892.     }
  893.  
  894.     if ("x" in Options)
  895.     for (Var in Map)
  896.         if (Map[Var] in Options)
  897.         printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
  898.         "/dev/stderr"
  899.         else
  900.         printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
  901.     return Ret
  902. }
  903.  
  904. # OptSets is a semicolon-separated list of sets of option sets.
  905. # Within a list of option sets, the option sets are separated by commas.  For
  906. # each set of sets, if any option in one of the sets is in Options[] AND any
  907. # option in one of the other sets is in Options[], an error string is returned.
  908. # If no conflicts are found, nothing is returned.
  909. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
  910. # the exclusions presented by the first set of sets (ab,def,g) if:
  911. # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
  912. # (a or b is in Options[]) AND (g is in Options) OR
  913. # (d, e, or f is in Options[]) AND (g is in Options)
  914. # An error will be returned due to the exclusions presented by the second set
  915. # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
  916. # todo: make options given on command line unset options given in config file
  917. # todo: that they conflict with.
  918. function ExclusiveOptions(OptSets,Options,
  919. Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
  920. SetNum,OSetNum) {
  921.     NumSetSets = split(OptSets,SetSets,";")
  922.     # For each set of sets...
  923.     for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
  924.     # NumSets is the number of sets in this set of sets.
  925.     NumSets = split(SetSets[SetSet],Sets,",")
  926.     # For each set in a set of sets except the last...
  927.     for (SetNum = 1; SetNum < NumSets; SetNum++) {
  928.         s1 = Sets[SetNum]
  929.         L1 = length(s1)
  930.         for (Pos1 = 1; Pos1 <= L1; Pos1++)
  931.         # If any of the options in this set was given, check whether
  932.         # any of the options in the other sets was given.  Only check
  933.         # later sets since earlier sets will have already been checked
  934.         # against this set.
  935.         if ((c1 = substr(s1,Pos1,1)) in Options)
  936.             for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
  937.             s2 = Sets[OSetNum]
  938.             L2 = length(s2)
  939.             for (Pos2 = 1; Pos2 <= L2; Pos2++)
  940.                 if ((c2 = substr(s2,Pos2,1)) in Options)
  941.                 ErrStr = ErrStr "\n"\
  942.                 sprintf("Cannot give both %s and %s options.",
  943.                 c1,c2)
  944.             }
  945.     }
  946.     }
  947.     if (ErrStr != "")
  948.     return substr(ErrStr,2)
  949.     return ""
  950. }
  951.  
  952. # The value of each instance of option Opt that occurs in Options[] is made an
  953. # index of Set[].
  954. # The return value is the number of instances of Opt in Options.
  955. function Opt2Set(Options,Opt,Set,  count) {
  956.     if (!(Opt in Options))
  957.     return 0
  958.     Set[Options[Opt]]
  959.     count = Options[Opt,"count"]
  960.     for (; count > 1; count--)
  961.     Set[Options[Opt,count]]
  962.     return count
  963. }
  964.  
  965. # The value of each instance of option Opt that occurs in Options[] that
  966. # begins with "!" is made an index of nSet[] (with the ! stripped from it).
  967. # Other values are made indexes of Set[].
  968. # The return value is the number of instances of Opt in Options.
  969. function Opt2Sets(Options,Opt,Set,nSet,  count,aSet,ret) {
  970.     ret = Opt2Set(Options,Opt,aSet)
  971.     for (value in aSet)
  972.     if (substr(value,1,1) == "!")
  973.         nSet[substr(value,2)]
  974.     else
  975.         Set[value]
  976.     return ret
  977. }
  978.  
  979. # Returns true if option Opt was given on the command line.
  980. function CmdLineOpt(Options,Opt,  i) {
  981.     for (i = 1; (Opt,"num",i) in Options; i++)
  982.     if (Options[Opt,"num",i] != 0)
  983.         return 1
  984.     return 0
  985. }
  986. ### End of ProcArgs library
  987.